

#include <dlfcn.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <unistd.h>
#include <sys/syscall.h>
// #include <sys/types.h>
#include <execinfo.h>
#include <elf.h>

typedef int (*lpfnpthread_create)(void *thread, const void *attr, void *(*start_routine) (void *), void *arg);

#define GETTID()    syscall(SYS_gettid)


#ifdef __x86_64
typedef Elf64_Ehdr  Elf_Ehdr;
typedef Elf64_Shdr  Elf_Shdr;
typedef Elf64_Sym   Elf_Sym;
typedef Elf64_Addr  Elf_Addr;
const char * PTHREADSO = "/lib/x86_64-linux-gnu/libpthread.so.0";
#else
typedef Elf32_Ehdr  Elf_Ehdr;
typedef Elf32_Shdr  Elf_Shdr;
typedef Elf32_Sym   Elf_Sym;
typedef Elf32_Addr  Elf_Addr;
const char * PTHREADSO = "/lib/libpthread.so.0";
#endif


class Args
{
public:
    Args() {}
    long int    old_tid;
    void* (*orig_start_routine)(void*);
    void* orig_arg;
};

static const char * addr2func(void * addr)
{/*{{{*/
    if(addr == NULL)
        return NULL;
    static char     *   buffer = NULL;
    Elf_Ehdr        *   ehdr;
    Elf_Shdr        *   shdr;
    Elf_Sym         *   sym;
    int                 len;
    FILE            *   fp;
    int                 bytes_read;
    int                 i;
    char            *   p;
    int                 SymCount;
    int                 SymIndex;
    int                 StrIndex;
    if(buffer == NULL) {
        fp = fopen("/proc/self/exe", "rb");
        if(fp == NULL) {
            perror("fopen");
            return NULL;
        }
        fseek(fp, 0, SEEK_END);
        len = ftell(fp);
        // PRINTF("file length = %d bytes.\n", len);
        fseek(fp, 0, SEEK_SET);
        buffer = (char *)malloc(len);
        if(buffer == NULL) {
            fclose(fp);
            return NULL;
        }
        printf("len = %d\n", len);
        bytes_read = fread(buffer, len, 1, fp);
        fclose(fp);
        if (bytes_read != 1) {
            ;;
        }
        // printf("bytes_read = %d.\n", bytes_read);
    }

    ehdr        = (Elf_Ehdr *)buffer;
    shdr        = (Elf_Shdr *)(buffer + ehdr->e_shoff);
    p           = buffer + shdr[ehdr->e_shstrndx].sh_offset;
    SymIndex    = -1;
    StrIndex    = -1;
    for(i=0; i<ehdr->e_shnum; i++) {
        // PRINTF("sym: %s\n", p + shdr[i].sh_name);
        if(shdr[i].sh_type == SHT_SYMTAB && strncmp(p + shdr[i].sh_name, ".symtab", 7) == 0) {
            SymIndex = i;
        }
        if(shdr[i].sh_type == SHT_STRTAB && strncmp(p + shdr[i].sh_name, ".strtab", 7) == 0) {
            StrIndex = i;
        }
    }
    if(SymIndex == -1) {
        printf("No symbol table.\n");
        return NULL;
    }
    if(StrIndex == -1) {
        printf("No string table.\n");
        return NULL;
    }

    sym         = (Elf_Sym *)(buffer + shdr[SymIndex].sh_offset);
    p           = (char *)(buffer + shdr[StrIndex].sh_offset);
    SymCount    = shdr[SymIndex].sh_size / sizeof(Elf_Sym);

    for(i=0; i<SymCount; i++) {
        if(sym[i].st_name != 0 && (Elf_Addr)addr == sym[i].st_value && (sym[i].st_info & 0xf) != STB_LOCAL) {
            return p + sym[i].st_name;
        }
    }

    int mind = -1;
    for (i = 0; i < SymCount; i++) {
        if ((Elf_Addr)addr > sym[i].st_value) {
            if (mind == -1) 
                mind = i;
            else if (sym[i].st_value > sym[mind].st_value)
                mind = i;
        }
    }
    if (mind != -1)
        return p + sym[mind].st_name;
    return NULL;
}/*}}}*/

static void showBackTrace()
{
    void* array[10];
    size_t  size = backtrace(array, 10);
    char** strings = backtrace_symbols(array, size);
    for (size_t i = 1; i < size; i++) {
        printf("<PRELOAD> caller: %lu: \t\t%s\t\t%s\n", i, strings[i], addr2func(array[i]));
    }
    free(strings);
}


static void* __new_routine(void* arg)
{
    Args* a = (Args*) arg;
    printf("a = %p\n", a);

    void* (*orig_start_routine)(void*) = a->orig_start_routine;
    void* orig_arg = a->orig_arg;

    printf("<PRELOAD> new thread(%ld) is running: entry is %p(%s), created by %ld\n", GETTID(), orig_start_routine, addr2func((void*)orig_start_routine), a->old_tid);
    showBackTrace();

    delete a;

    void* ret = orig_start_routine(orig_arg);
    return ret;
}


extern "C" int pthread_create(void *thread, const void *attr, void *(*start_routine) (void *), void *arg)
{
    printf("<PRELOAD> hack pthread_create. current threadid: %ld, routine: %p(%s)\n", GETTID(), start_routine, addr2func((void*)start_routine));
    showBackTrace();

    void* handle = dlopen(PTHREADSO, RTLD_LAZY);
    if (handle == NULL) {
        printf("<PRELOAD> dlopen: %s\n", dlerror());
        abort();
        return -1;
    }

    lpfnpthread_create lpfn = (lpfnpthread_create)dlsym(handle, "pthread_create");
    if (lpfn == NULL) {
        printf("<PRELOAD> dlsym: %s\n", dlerror());
        dlclose(handle);
        abort();
        return -1;
    }

    Args*   a = new Args();
    a->old_tid = GETTID();
    a->orig_start_routine = start_routine;
    a->orig_arg = arg;

    int ret = lpfn(thread, attr, __new_routine, a);
    dlclose(handle);
    return ret;
}



